/**
 * Launch this script to re-generate the files containing the list of extensions
 * being used by each example.
 */
const gd = require('../public/libGD.js')();
const { mapFor, mapVector } = require('./lib/MapFor');
const makeExtensionsLoader = require('./lib/LocalJsExtensionsLoader');
const { getExampleNames } = require('./lib/ExamplesLoader');
const {
  readProjectFile,
  loadSerializedProject,
} = require('./lib/LocalProjectOpener');
const fs = require('fs');
const _ = require('lodash');
var shell = require('shelljs');

shell.exec('node import-GDJS-Runtime.js');
gd.initializePlatforms(); //TODO: Useless or not?

const outputFile = '../src/ProjectCreation/ExamplesInformation.js';

const getObjectTypes = projectOrLayout => {
  return _.uniq(
    mapFor(0, projectOrLayout.getObjectsCount(), i =>
      projectOrLayout.getObjectAt(i).getType()
    )
  );
};

const getBehaviorTypes = projectOrLayout => {
  return _.uniq(
    _.flatten(
      mapFor(0, projectOrLayout.getObjectsCount(), i => {
        const object = projectOrLayout.getObjectAt(i);
        const allBehaviorNames = object.getAllBehaviorNames();
        return mapVector(allBehaviorNames, behaviorName => {
          return object.getBehavior(behaviorName).getTypeName();
        });
      })
    )
  );
};

const getEventsAndInstructionsTypes = (project, events) => {
  const eventsTypesLister = new gd.EventsTypesLister(project);
  eventsTypesLister.launch(events);
  const types = {
    events: _.uniq(eventsTypesLister.getAllEventsTypes().toJSArray()),
    conditions: _.uniq(eventsTypesLister.getAllConditionsTypes().toJSArray()),
    actions: _.uniq(eventsTypesLister.getAllActionsTypes().toJSArray()),
  };
  eventsTypesLister.delete();
  return types;
};

const computeUsedExtensions = project => {
  // TODO: gd.WholeProjectRefactorer.exposeProjectEvents should be used to browse events
  // so that events functions are also browsed.
  const layoutExtensions = _.flatten(
    mapFor(0, project.getLayoutsCount(), i => {
      const layout = project.getLayoutAt(i);
      const extensionsFromObjects = getObjectTypes(layout).map(objectType => {
        return gd.MetadataProvider.getExtensionAndObjectMetadata(
          project.getCurrentPlatform(),
          objectType
        ).getExtension();
      });
      const extensionsFromBehaviors = getBehaviorTypes(layout).map(
        behaviorType => {
          return gd.MetadataProvider.getExtensionAndBehaviorMetadata(
            project.getCurrentPlatform(),
            behaviorType
          ).getExtension();
        }
      );
      const events = layout.getEvents();
      const eventsAndInstructionsTypes = getEventsAndInstructionsTypes(
        project,
        events
      );
      const extensionsFromConditions = eventsAndInstructionsTypes.conditions.map(
        conditionType => {
          return gd.MetadataProvider.getExtensionAndConditionMetadata(
            project.getCurrentPlatform(),
            conditionType
          ).getExtension();
        }
      );
      const extensionsFromActions = eventsAndInstructionsTypes.actions.map(
        actionType => {
          return gd.MetadataProvider.getExtensionAndActionMetadata(
            project.getCurrentPlatform(),
            actionType
          ).getExtension();
        }
      );

      return _.uniq([
        ...extensionsFromObjects,
        ...extensionsFromBehaviors,
        ...extensionsFromConditions,
        ...extensionsFromActions,
      ]);
    })
  );
  const externalEventsExtensions = _.flatten(
    mapFor(0, project.getExternalEventsCount(), i => {
      const externalEvents = project.getExternalEventsAt(i);

      const events = externalEvents.getEvents();
      const eventsAndInstructionsTypes = getEventsAndInstructionsTypes(
        project,
        events
      );
      const extensionsFromConditions = eventsAndInstructionsTypes.conditions.map(
        conditionType => {
          return gd.MetadataProvider.getExtensionAndConditionMetadata(
            project.getCurrentPlatform(),
            conditionType
          ).getExtension();
        }
      );
      const extensionsFromActions = eventsAndInstructionsTypes.actions.map(
        actionType => {
          return gd.MetadataProvider.getExtensionAndActionMetadata(
            project.getCurrentPlatform(),
            actionType
          ).getExtension();
        }
      );

      return _.uniq([...extensionsFromConditions, ...extensionsFromActions]);
    })
  );

  return _.uniq([...layoutExtensions, ...externalEventsExtensions]);
};

const writeFile = object => {
  return new Promise((resolve, reject) => {
    const content = [
      `// This file is generated by update-examples-extensions-usage-from-resources-examples.js script`,
      `// prettier-ignore`,
      `module.exports = ${JSON.stringify(object, null, 2)};`,
      ``,
    ].join('\n');
    fs.writeFile(outputFile, content, err => {
      if (err) return reject(err);

      resolve();
    });
  });
};

const readFileContent = filename => {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, 'utf8', (err, content) => {
      if (err) return reject(err);

      resolve(content);
    });
  });
};

const noopTranslationFunction = str => str;
const examplesInformation = {};
const extensionsLoader = makeExtensionsLoader({ gd, filterExamples: false });
extensionsLoader
  .loadAllExtensions(noopTranslationFunction)
  .then(loadingResults => {
    console.info('Loaded extensions', loadingResults);

    return getExampleNames();
  })
  .then(
    exampleNames => {
      return Promise.all(
        exampleNames.map(exampleName => {
          const exampleInformation = (examplesInformation[exampleName] = {
            description: '',
            usedExtensions: [],
          });

          return Promise.all([
            readProjectFile(
              `../resources/examples/${exampleName}/${exampleName}.json`
            )
              .then(projectObject => {
                console.log(`Example "${exampleName}" loaded.`);

                const project = loadSerializedProject(gd, projectObject);
                const usedExtensions = computeUsedExtensions(project);
                exampleInformation.usedExtensions = usedExtensions.map(
                  extension => ({
                    fullName: extension.getFullName(),
                    name: extension.getName(),
                  })
                );
              })
              .catch(error => {
                console.error('Error caught while analyzing game:', error);
              }),
            readFileContent(
              `../resources/examples/${exampleName}/README.md`
            ).then(
              readmeContent => {
                exampleInformation.description = readmeContent;
              },
              error => {
                console.error(
                  `⚠️ No/invalid README found for ${exampleName}:`,
                  error
                );
              }
            ),
          ]);
        })
      );
    },
    err => console.error('Error while loading extensions', err)
  )
  .then(() => {
    return writeFile(examplesInformation);
  })
  .then(
    () => console.info('Done.'),
    err => console.error('Error while writing output', err)
  );
